在封存整個專案之前,還是先回頭再看一眼,現在的代理人到底理解了什麼。分明還沒有達成目標,為什麼就封存?正是因為還沒有達成目標,所以封存吧。回顧首日的目標,
DeltaPathogen 專案的目的在於,針對疫途這個遊戲,訓練出可以擊敗真實玩家的代理人。
然而,目前僅有的是,能夠以些微優勢擊敗隨機猴子的代理人。達到這個目標為止的投入,也已經非常消耗了。不得不說有點遺憾,但已經可以正式在結語之前宣告,這個專案失敗了。自己不夠努力、沒有投入足夠的時間與機器資源等等,是有意為之,所以並非這遺憾的主體;遺憾之處在於專案進行中的每一個小地方,都有無數的問題從各個維度延展出來,而我除了信仰之外沒有工具可以應對。針對參考書的信仰、大神部落格的信仰,或是自己的盲信之躍,或是上述的反覆切換。遺憾之處在於,各個維度延伸出問題也就罷了,試著加以量化之後,也不確定應該如何理解。先射箭再畫靶已是一種屈辱,而再有甚者連靶也畫不出來,從中獲得的,只能說是一種樂趣。
沒有利害關係,沒有賴以維生的壓力,也沒有特別值得持續延伸的基業,也沒有確實的理解。就是玩耍。不過仍然要定位為遺憾,作為一種提醒,也許有機會像赫庫蘭尼姆紙莎草紙卷一樣,終究有機會等到 3D CT 掃描技術和機器學習技術的出現。嗯,但是也有可能,也許我也已經手握所有足夠的工具,但是由於自己無法良好地駕馭這些工具,所以也沒來得及完成有意義的目標。所以遺憾。
真是抱歉,撇開我個人沒學到什麼,應該進到今天的主題,這個代理人學到了什麼。這篇文的初稿是在 8/23 連假的時候,初步分析當時 gen7 的模型行為,當時的分析也促進了相當於系列文當中 Day8 ~ Day10 的部份改動。這次當然是會針對昨天為止的訓練成果更新發現。
懶得架設一個 notebook 起來(不知怎的,我的 notebook 上面特別容易說有 CUDA error,後來就沒再試了),所以有互動需求的時候一直是很浪漫地直接開 python console 起來使用。
$ cd examples/coord_clients/
$ python
Python 3.12.3 (main, Apr 23 2024, 09:16:07) [GCC 13.2.1 20240417] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> import torch
>>> import reinforcement_network
>>> import constant
大致上引用了需要的套件,然後載入模型(同昨日描述,由於能力不佳,便不直接公開,有興趣的讀者仍然可以找我索取),
>>> m = torch.load("../../share/20240923_gen23/train-trial6/game-trial6.pth.4").to('cpu')
>>> m
PathogenNet(
(conv0): Conv2d(24, 100, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn0): BatchNorm2d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu0): ReLU(inplace=True)
(resblocks0): ModuleList(
(0-6): 7 x PathogenResidualBlock(
(conv1): Conv2d(100, 100, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn1): BatchNorm2d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(100, 100, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn2): BatchNorm2d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(policy_conv): Conv2d(100, 1, kernel_size=(1, 1), stride=(1, 1))
(policy_bn): BatchNorm2d(1, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(policy_fc): Linear(in_features=49, out_features=61, bias=True)
(value_conv): Conv2d(100, 1, kernel_size=(1, 1), stride=(1, 1))
(value_bn): BatchNorm2d(1, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(value_fc): Linear(in_features=49, out_features=1, bias=True)
(valid_conv): Conv2d(100, 1, kernel_size=(1, 1), stride=(1, 1))
(valid_bn): BatchNorm2d(1, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(valid_fc): Linear(in_features=49, out_features=61, bias=True)
然後,讀取資料集檔案。先看看它對於殘局的表現?引用先前的殘局產生器的一部份(隨意選取一個 offset)
>>> f = open('../../share/20240911_gen16/simulation/tsume.eval.bin', 'rb')
>>> f.seek(4096*2346, 0)
>>> D = f.read(4096)
然後,先前在 reinforcement_network
裡面已經做好的 dataset 可以用來拆解針對疫途遊戲使用的資料集格式,
# partition an entry into the input and the three heads of output
def partition(x):
start = 0
end = D_STATE
state = torch.from_numpy(np.frombuffer(x[start:end], dtype=np.float32).copy()).to(torch.device("cpu")).unsqueeze(0)
start = start + D_STATE
end = end + D_POLICY
policy = torch.from_numpy(np.frombuffer(x[start:end], dtype=np.float32).copy()).to(torch.device("cpu")).unsqueeze(0)
start = start + D_POLICY
end = end + D_VALID
valid = torch.from_numpy(np.frombuffer(x[start:end], dtype=np.float32).copy()).to(torch.device("cpu")).unsqueeze(0)
start = start + D_VALID
end = end + D_VALUE
value = torch.from_numpy(np.frombuffer(x[start:end], dtype=np.float32).copy()).to(torch.device("cpu")).unsqueeze(0)
return state, policy, valid, value
使用這個 partition
功能,
>>> state, policy, valid, value = reinforcement_network.partition(D)
然後使用模型推論這個 state
。這是只要下出正確著手就可以獲勝的殘局,而它的評價(value_pred
)也蠻認同的樣子;策略也是有學對(42,換算起來是 (-1, -1)
,所以這是一個羅盤階段的選點)
>>> policy_pred, valid_pred, value_pred = m(state)
>>> value
tensor([[1.]])
>>> value_pred
tensor([[0.9910]])
>>> torch.argmax(policy)
tensor(42)
>>> torch.argmax(policy_pred)
tensor(42)
至於規則的理解,也就是合法性的部份,可以觀察一下分佈。還算是蠻不錯的。以千分之一當作閾值,可以篩選到全部的合法選點,小於千分之三的漏網之魚僅有兩個選點。
>>> valid_prob = torch.softmax(valid_pred, dim=1)
>>> torch.where(valid_prob < 0.001, torch.tensor(0.0), valid_prob)
tensor([[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.3985, 0.0748, 0.0000,
0.0000, 0.0000, 0.4366, 0.0000, 0.0018, 0.0000, 0.0000, 0.0535, 0.0320,
0.0026, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]])
>>> valid
tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 1., 0., 0., 0., 0., 1., 1.,
0., 0., 0., 0., 0., 0., 0.]])
不對,這,有點太不錯了?也許這個驗證集先前曾經被當作過後來世代的訓練集?我決定重新產生一個殘局看看效果。
重新生成一局的殘局譜,蠻好用的,使用 tsume.sh
,重新執行上述步驟,可以觀察到的是:
>>> nf = open('../../share/20240923_gen23/simulation-trial1/misc/new_tsume.raw.bin', 'rb')
>>> D = nf.read(4096)
>>> state, policy, valid, value = reinforcement_network.partition(D)
>>> policy_pred, valid_pred, value_pred = m(state)
>>> value
tensor([[1.]])
>>> value_pred
tensor([[-1.0000]])
唉,完全搞反了,而且還很有自信的樣子!真不知道是怎麼來的自信。我猜,到頭來它還是沒能夠從我的資料集格式當中解譯出自己應該站在哪個陣營來設想的概念。
>>> valid
tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 1., 0., 1., 1.,
1., 1., 0., 1., 1., 1., 0.]])
>>> valid_prob = torch.softmax(valid_pred, dim=1)
>>> torch.where(valid_prob < 0.001, torch.tensor(0.0), valid_prob)
tensor([[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0018, 0.0158, 0.0000, 0.0045, 0.0011, 0.0442, 0.1619,
0.1954, 0.0278, 0.0000, 0.1126, 0.3487, 0.0857, 0.0000]])
合法性倒是不讓人擔心。
>>> policy
tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 1., 0., 0., 0.]])
>>> torch.argmax(policy)
tensor(57)
>>> torch.argmax(policy_pred)
tensor(47)
>>> torch.topk(policy_pred, 10)
torch.return_types.topk(
values=tensor([[ -8.1673, -8.8501, -9.5433, -9.6064, -10.5296, -10.7374, -11.0263,
-11.1897, -11.2158, -11.3856]]),
indices=tensor([[47, 48, 52, 53, 59, 57, 54, 42, 58, 51]]))
唉,策略的部份,唯一正手,竟然淪落到第六選點。
往後一手,再觀察看看:
>>> f.seek(4096*1, 0)
4096
>>> D = nf.read(4096)
KeyboardInterrupt
>>> nf.seek(4096*1, 0)
4096
>>> D = nf.read(4096)
>>> state, policy, valid, value = reinforcement_network.partition(D)
>>> policy_pred, valid_pred, value_pred = m(state)
>>> torch.argmax(policy)
tensor(4)
>>> torch.topk(policy_pred, 10)
torch.return_types.topk(
values=tensor([[ -6.6372, -6.6382, -12.0474, -13.1279, -13.6133, -13.6623, -13.8067,
-14.7163, -16.0534, -16.3246]]),
indices=tensor([[ 4, 2, 11, 3, 9, 8, 0, 1, 10, 5]]))
>>> valid
tensor([[0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0.]])
>>> valid_prob = torch.softmax(valid_pred, dim=1)
>>> torch.where(valid_prob < 0.001, torch.tensor(0.0), valid_prob)
tensor([[0.0000, 0.0000, 0.0038, 0.0000, 0.9962, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]])
>>> value
tensor([[1.]])
>>> value_pred
tensor([[-1.0000]])
還可以,一樣是價值判斷不太滿意。
在第 18 天有提過這個功能。
>>> nf.seek(4096*6, 0)
24576
>>> D = nf.read(4096)
>>> state, policy, valid, value = reinforcement_network.partition(D)
>>> policy_pred, valid_pred, value_pred = m(state)
>>> value
tensor([[-1.]])
>>> value_pred
tensor([[0.9999]])
一樣是價值判斷有問題。它可能學到變得很極端,是嗎?
>>> policy
tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0.]])
>>> torch.topk(policy_pred, 10)
torch.return_types.topk(
values=tensor([[ -9.4116, -15.0653, -17.7555, -18.0777, -18.9111, -18.9563, -18.9941,
-19.5039, -19.7391, -19.7568]]),
indices=tensor([[14, 24, 17, 20, 23, 28, 18, 22, 27, 29]]))
>>> valid
tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0.]])
>>> valid_prob = torch.softmax(valid_pred, dim=1)
>>> torch.where(valid_prob < 0.001, torch.tensor(0.0), valid_prob)
tensor([[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.9984, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0014, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]])
在倒數第二手失敗的部份,只有選點唯一時才會被我收錄進來,所以這一步,策略和合法性頗為類同。
也算是留個紀錄,如果日後回顧的時候,可以從這裡推敲思考,有什麼方法可以用來補足。